import $ from 'jquery';
import {
  copyInsertRowProperties,
  createColResizePlaceholder,
  createResizeMask,
  createRowResizePlaceholder,
  createTd,
  createTdPlaceholder,
  createTr,
  getPosition,
  getPositionNumber,
  getTargetForTd,
  getTdCol,
  getTdHeight,
  getTdRow,
  hideTd,
  hideTdBorder,
  initSpan,
  isHideTd,
  isIgnoreMousedownForDesignTable,
  removeResizeMask,
  setTdHeight,
  showTd,
  showTdBorder,
  stop
} from '@/components/hhd/design/DesignTable/TableHelper';
import {ContextMenu} from '@/components/hhd/design/DesignTable/ContextMenu';
import {guid} from '@/utils';

const documentMouseDownEvent = {};

export default class DesignTableAction {
  root;
  config;
  dataActionKey;

  bind(el, config) {
    this.root = el;
    this.config = config;
    this.bindSelect(el);
    this.bindClearSelect(el);
    this.bindEdit(el);
    this.bindContext();
    // this.initWidth(el);
    this.bindResize(el);
    // this.clearSelected();
    this.change();
    this.resolvePlaceholder();
  }

  destory() {
    let key = this.dataActionKey;
    documentMouseDownEvent[key] && $(window.document.body).off('mousedown', documentMouseDownEvent[key]);
  }

  reBind() {
    this.bind(this.root, this.config);
  }

  change() {
    this.config && this.config.change && this.config.change();
  }

  bindEdit(el) {
    let that = this;
    let editing = undefined;
    let content = undefined;

    function click(e) {
      let td = getTargetForTd(e.target);
      if (td && td === editing) return;
      that.editingStyle();
      $(window.document.body).off('click');
      that.change();
    }

    function dblclick(e) {
      if (that.disabledAction('edit')) return;
      that.clearSelected();
      let td = getTargetForTd(e.target);
      if (!td || $(td).find('.field-component').length) return;
      editing = td;
      content = $(td).find('.content-wrapper');
      if (!content.length) {
        $(td).append(content = $('<div class="content-wrapper"/>'));
      }
      $(td).find('.cell-placeholder').remove();
      $(content).attr('contentEditable', 'true');
      $(content).focus();
      $(content).click();
      $(window.document.body).off('click').on('click', click);
    }

    $(el).find('td').off('dblclick').on('dblclick', dblclick);
  }

  editingStyle() {
    $(this.root).find('.content-wrapper[contenteditable=true]').removeAttr('contenteditable');
    let wrappers = $(this.root).find('.content-wrapper');
    for (let i = 0; i < wrappers.length; i++) {
      let wrapper = wrappers[i];
      let placeholder = $(wrapper).parent().attr('data-placeholder');

      if (!wrapper.textContent && !$(wrapper).children().length) {
        placeholder && $(wrapper).parent().append(createTdPlaceholder(placeholder));
        $(wrapper).remove();
      }
    }
  }

  resolvePlaceholder() {
    let tds = $(this.root).find('td');
    for (let i = 0; i < tds.length; i++) {
      let td = tds[i], ps = $(td).find('.cell-placeholder');
      if ($(td).children().length <= 2) {
        let placeholder = $(td).attr('data-placeholder');
        placeholder && $(td).append(createTdPlaceholder(placeholder));
      } else {
        if ($(td).children().length === 3 && ps.length === 1) {
        } else {
          ps.remove();
        }
      }
    }
  }

  bindContext() {
    let that = this;

    function contextmenu(e) {
      stop(e);
      let target = getTargetForTd(e.target);
      if (!target || !$(target).hasClass('selected')) return;
      if (that.disabledAction('context')) {
        return;
      }
      that.openContextMenu(e);
    }

    $(this.root).find('td').off('contextmenu').on('contextmenu', contextmenu);
  }

  unmergeSelected() {
    let bound = this.getSelectedBound();
    this.forEachBoundTd(bound, td => {
      initSpan(td);
      showTd(td);
    });
    this.resetSelectedBound();
  }

  mergeSelected() {
    let bound = this.getSelectedBound(), first = true;
    this.forEachBoundTd(bound, td => {
      if (!first) {
        hideTd(td);
      } else {
        $(td).attr('rowspan', bound.maxRow - bound.minRow + 1);
        $(td).attr('colspan', bound.maxCol - bound.minCol + 1);
      }
      first = false;
    });
    this.resetSelectedBound();
  }

  getSelectedBound() {
    let tds = this.getSelectedTds() || [];
    let bound = {minRow: 1000, maxRow: -1, minCol: 1000, maxCol: -1};
    for (let i = 0; i < tds.length; i++) {
      let td = tds[i];
      let row = getTdRow(td);
      let col = getTdCol(td);
      bound.minRow = Math.min(bound.minRow, row);
      bound.maxRow = Math.max(bound.maxRow, row);
      bound.minCol = Math.min(bound.minCol, col);
      bound.maxCol = Math.max(bound.maxCol, col);
    }
    return bound;
  }

  closeContextMenu() {
    this.contextMenu && this.contextMenu.close();
  }

  openContextMenu({clientX, clientY}) {
    this.contextMenu = new ContextMenu([
      {
        title: '合并',
        icon: '&#xe780;',
        click: (close) => close() || this.mergeSelected() || this.change()
      },
      {
        title: '取消合并',
        icon: '&#xe674;',
        click: (close) => close() || this.unmergeSelected() || this.change()
      },
      {
        type: 'divider'
      },
      {
        title: '在上插入一行',
        icon: '&#xe675;',
        click: (close) => close() || this.insertRowBefore() || this.change()
      },
      {
        title: '在下插入一行',
        icon: '&#xe676;',
        click: (close) => close() || this.insertRowAfter() || this.change()
      },
      {
        title: '在左插入一列',
        icon: '&#xe673;',
        click: (close) => close() || this.insertColBefore() || this.change()
      },
      {
        title: '在右插入一列',
        icon: '&#xe625;',
        click: (close) => close() || this.insertColAfter() || this.change()
      },
      {
        type: 'divider'
      },
      {
        title: '删除行',
        icon: '&#xe642;',
        click: (close) => close() || this.removeRows() || this.change()
      },
      {
        title: '删除列',
        icon: '&#xe794;',
        click: (close) => close() || this.removeCols() || this.change()
      },
      {
        type: 'divider'
      },
      {
        title: '平均宽度',
        icon: '&#xe83b;',
        click: (close) => close() || this.evenWidth() || this.change()
      },
      {
        title: '平均高度',
        icon: '&#xe60f;',
        click: (close) => close() || this.evenHeight() || this.change()
      },
      {
        type: 'divider'
      },
      {
        title: '清除样式',
        icon: '&#xe60a;',
        click: (close) => close() || this.clearStyle() || this.change()
      },
      {
        title: '清除内容',
        icon: '&#xe7bc;',
        click: (close) => close() || this.clearContent() || this.change()
      },
      {
        title: '重置单元格',
        icon: '&#xe604;',
        click: (close) => close() || this.reSetSetTds() || this.change()
      }
    ], {left: clientX, top: clientY});
  }

  insertRowBefore() {
    let {minRow} = this.getSelectedBound();
    let tr = $(this.root).find('tr')[minRow];
    let col = $(this.root).find('col').length;
    this.clearSelected();
    let newTr = createTr(col);
    $(tr).before(newTr);
    copyInsertRowProperties(tr, newTr);

    this.resolvePositions();
    this.reBind();
    this.resolveSpans();
  }

  insertRowAfter() {
    let {maxRow} = this.getSelectedBound();
    let tr = $(this.root).find('tr')[maxRow];
    let col = $(this.root).find('col').length;
    this.clearSelected();
    let newTr = createTr(col);
    $(tr).after(newTr);
    copyInsertRowProperties(tr, newTr);

    this.resolvePositions();
    this.reBind();
    this.resolveSpans();
  }

  insertColBefore() {
    let {minCol} = this.getSelectedBound();
    let trs = $(this.root).find('tr');
    for (let i = 0; i < trs.length; i++) {
      let tr = trs[i];
      $($(tr).find('td')[minCol]).before($(createTd(minCol)));
    }

    let cols = $(this.root).find('col'), width = cols[minCol].offsetWidth, windthLeft = parseInt(width / 2);
    cols[minCol].style.width = `${windthLeft}px`;

    let col = document.createElement('col');
    col.style.width = `${width - windthLeft}px`;
    $(cols[minCol]).before(col);

    this.resolvePositions();
    this.reBind();
    this.resolveSpans();
  }

  insertColAfter() {
    let {maxCol} = this.getSelectedBound();
    let trs = $(this.root).find('tr');
    for (let i = 0; i < trs.length; i++) {
      let tr = trs[i];
      $($(tr).find('td')[maxCol]).after($(createTd(maxCol)));
    }

    let cols = $(this.root).find('col'), width = cols[maxCol].offsetWidth, windthLeft = parseInt(width / 2);
    cols[maxCol].style.width = `${windthLeft}px`;

    let col = document.createElement('col');
    col.style.width = `${width - windthLeft}px`;
    $(cols[maxCol]).after(col);

    this.resolvePositions();
    this.reBind();
    this.resolveSpans();
  }

  removeCols() {
    let {minCol, maxCol} = this.getSelectedBound();
    this.clearSelected();
    let trs = $(this.root).find('tr');
    for (let i = 0; i < trs.length; i++) {
      let tds = $(trs[i]).find('td');
      if (tds.length <= 1) return;
      for (let j = 0; j < tds.length; j++) {
        if (j >= minCol && j <= maxCol) {
          if($(tds[j]).attr('data-sub-table-role') === 'sub-form-button-container')  {
            $(tds[j]).next().remove();
          } else {
            $(tds[j]).remove();
          }
        }
      }
    }

    let cols = $(this.root).find('col');
    for (let i = 0; i < cols.length; i++) {
      if (i >= minCol && i <= maxCol) {
        $(cols[i]).remove();
      }
    }

    this.resolvePositions();
    this.resolveSpans();
  }

  evenWidth() {
    let cols = $(this.root).find('col');
    let {minCol, maxCol} = this.getSelectedBound();
    if (minCol === maxCol) return;
    let width = 0;
    for (let i = 0; i < cols.length; i++) {
      if (i >= minCol && i <= maxCol) {
        width += cols[i].offsetWidth;
      }
    }

    let evenWidth = parseInt(width / (maxCol - minCol + 1)), count = 0;
    for (let i = 0; i < cols.length; i++) {
      if (i >= minCol && i <= maxCol) {
        cols[i].style.width = `${evenWidth}px`;
        count += evenWidth;
      }
    }
    count -= evenWidth;
    cols[maxCol] = `${width - count}px`;
  }

  evenHeight() {
    let trs = $(this.root).find('tr');
    let {minRow, maxRow} = this.getSelectedBound();
    if (minRow === maxRow) return;
    let height = 0;
    for (let i = 0; i < trs.length; i++) {
      if (i >= minRow && i <= maxRow) {
        height += trs[i].offsetHeight;
      }
    }

    let evenHeight = parseInt(height / (maxRow - minRow + 1)), count = 0;
    for (let i = 0; i < trs.length; i++) {
      if (i >= minRow && i <= maxRow) {
        trs[i].style.height = `${evenHeight}px`;
        count += evenHeight;
      }
    }
    count -= evenHeight;
    trs[maxRow] = `${height - count}px`;
  }

  clearStyle() {
    let bound = this.getSelectedBound();
    this.forEachBoundTd(bound, td => {
      td.parentNode.style = 'height: 30px';
      $(td).removeAttr('rowspan');
      $(td).removeAttr('colspan');
      td.style = '';
    });

    this.evenWidth();
    this.evenHeight();
    this.clearSelected();
  }

  clearContent() {
    let bound = this.getSelectedBound();
    this.forEachBoundTd(bound, td => {
      $(td).find('.content-wrapper').html('');
    });
    this.clearSelected();
  }

  reSetSetTds() {
    let bound = this.getSelectedBound();
    this.forEachBoundTd(bound, td => {
      td.parentNode.style = 'height: 40px';
      $(td).removeAttr('rowspan');
      $(td).removeAttr('colspan');
      $(td).find('.content-wrapper').remove();
      td.style = '';
    });
    this.evenWidth();
    this.evenHeight();
    this.clearSelected();
  }

  removeRows() {
    let {minRow, maxRow} = this.getSelectedBound();
    this.clearSelected();
    let trs = $(this.root).find('tr');
    if (trs.length === 1) return;
    for (let i = 0; i < trs.length; i++) {
      if (i >= minRow && i <= maxRow) {
        $(trs[i]).remove();
      }
    }

    this.resolvePositions();
    this.resolveSpans();
  }

  resolveSpans() {
    let tds = $(this.root).find('td'), list = [];
    for (let i = 0; i < tds.length; i++) {
      let td = tds[i];
      list.push(td);
      showTd(td);
    }

    list.forEach(td => {
      if (isHideTd(td)) return;
      let {row, col} = this.getTdSpan(td);
      let span = {row: 1, col: 1};
      let tdRow = getTdRow(td), tdCol = getTdCol(td);
      for (let i = tdRow; i < tdRow + row; i++) {
        for (let j = tdCol; j < tdCol + col; j++) {
          if (i === tdRow && j === tdCol) continue;
          let td = this.getTd(i, j);
          if (td) {
            hideTd(td);
            span.row = Math.max(i - tdRow + 1, span.row);
            span.col = Math.max(j - tdCol + 1, span.col);
          }
        }
      }
      $(td).attr('colspan', span.col);
      $(td).attr('rowspan', span.row);
    });
  }

  resolvePositions() {
    let trs = $(this.root).find('tr');
    for (let i = 0; i < trs.length; i++) {
      let tr = trs[i];
      $(tr).attr('data-row', i);
      let tds = $(tr).find('td');
      for (let j = 0; j < tds.length; j++) {
        let td = tds[j];
        $(td).attr('data-col', j);
      }
    }
  }

  getSelectedTds() {
    return $(this.root).find('.selected');
  }

  bindClearSelect(el) {
    let that = this;

    function mousedown(e) {
      if (e.which !== 1 || isIgnoreMousedownForDesignTable(e.target)) return;
      let target = e.target;
      while (target && target !== that.root) target = target.parentNode;
      if (target) return;
      that.clearSelected();
    }

    if (!$(this.root).attr('data-action-key')) {
      $(this.root).attr('data-action-key', guid());
    }

    let key = this.dataActionKey = $(this.root).attr('data-action-key');
    documentMouseDownEvent[key] && $(window.document.body).off('mousedown', documentMouseDownEvent[key]);
    $(window.document.body).off('mousedown', mousedown);
    documentMouseDownEvent[key] = mousedown;
    $(window.document.body).on('mousedown', mousedown);
  }

  bindSelect(el) {
    let startPoint = undefined, endPoint = undefined, that = this;

    function mouseover(e) {
      that.clearSelected();
      let target = getTargetForTd(e.target);
      if (!target) return;
      endPoint = getPosition(target);
      that.setSelectedStyle(startPoint, endPoint);
    }

    function mouseup() {
      $(el).find('td').off('mouseover');
      $(window).off('mouseup');
      that.emitSelected();
    }

    function mousedown(e) {
      if (e.which !== 1) return;
      that.clearSelected();
      let target = getTargetForTd(e.target);
      if (!target || that.isEditing(target)) return;
      startPoint = getPosition(target);
      that.setSelectedStyle(startPoint, startPoint);

      $(el).find('td').off('mouseover').on('mouseover', mouseover);
      $(window).off('mouseup').on('mouseup', mouseup);
    }

    $(el).find('td').off('mousedown').on('mousedown', mousedown);
  }

  emitSelected() {
    let selection = new Selection(this.root, this);
    let {maxRow} = this.getSelectedBound();
    if (maxRow < 0) selection = undefined;
    this.config && this.config.selected && this.config.selected(selection);
  }


  setSelectedStyle(startPoint, endPoint) {
    let tempStart = {row: Math.min(startPoint.row, endPoint.row), col: Math.min(startPoint.col, endPoint.col)},
      tempEnd = {row: Math.max(startPoint.row, endPoint.row), col: Math.max(startPoint.col, endPoint.col)};
    startPoint = tempStart;
    endPoint = tempEnd;

    while (true) {
      let tempStartPoint = {...startPoint}, tempEndPoint = {...endPoint};
      this.forEachTd(tempStartPoint, tempEndPoint, td => {
        function resolvePoint({row, col}) {
          tempStartPoint.row = Math.min(tempStartPoint.row, row);
          tempStartPoint.col = Math.min(tempStartPoint.col, col);

          tempEndPoint.row = Math.max(tempEndPoint.row, row);
          tempEndPoint.col = Math.max(tempEndPoint.col, col);
        }

        if (isHideTd(td)) {
          let coverTd = this.getCoverTd(td), span = this.getTdSpan(coverTd);
          resolvePoint({row: getTdRow(coverTd), col: getTdCol(coverTd)});
          resolvePoint({row: getTdRow(coverTd) + span.row - 1, col: getTdCol(coverTd) + span.col - 1});
        } else {
          let span = this.getTdSpan(td);
          resolvePoint({row: getTdRow(td) + span.row - 1, col: getTdCol(td) + span.col - 1});
          resolvePoint({row: getTdRow(td), col: getTdCol(td)});
        }
      });

      if (startPoint.row === tempStartPoint.row
        && startPoint.col === tempStartPoint.col
        && tempEndPoint.row === endPoint.row
        && tempEndPoint.col === endPoint.col) {
        break;
      }
      startPoint = tempStartPoint;
      endPoint = tempEndPoint;
    }

    this.forEachTd(startPoint, endPoint, td => {
      $(td).addClass('selected');
    });

    this.createSelectBound(startPoint, endPoint);
  }

  createSelectBound(startPoint, endPoint) {
    let startTd = this.getTd(startPoint.row, startPoint.col);
    let endTd = this.getTd(endPoint.row, endPoint.col);

    let startRect = startTd.getBoundingClientRect();
    let endRect = endTd.getBoundingClientRect();
    let rootRect = this.root.getBoundingClientRect();

    endTd = this.getCoverTd(endTd);

    this.forEachTd(startPoint, endPoint, td => {
      if (!isHideTd(td)) {
        let rect = td.getBoundingClientRect();
        if (endRect.height + endRect.y <= rect.height + rect.y) {
          endRect.y = rect.y;
          endRect.height = rect.height;
        }

        if (endRect.width + endRect.x <= rect.width + rect.x) {
          endRect.x = rect.x;
          endRect.width = rect.width;
        }
      }
    });

    let left = 0;
    let top = 0;
    let height = 0;
    let width = 0;

    if (!this.selectBound) {
      let div = document.createElement('div');
      div.className = 'select-bound';
      this.selectBound = div;
    }
    let div = this.selectBound;
    left = startRect.x - rootRect.x;
    top = startRect.y - rootRect.y;

    height = endRect.y - startRect.y + endTd.offsetHeight;
    width = endRect.x - startRect.x + endTd.offsetWidth;

    div.style.left = `${Math.ceil(left - 1)}px`;
    div.style.width = `${Math.ceil(width + 1)}px`;

    div.style.top = `${Math.ceil(top - 1)}px`;
    div.style.height = `${Math.ceil(height + 1)}px`;
    this.root.append(div);
  }

  forEachBoundTd(bound, callback) {
    this.forEachTd({row: bound.minRow, col: bound.minCol},
      {row: bound.maxRow, col: bound.maxCol}, callback);
  }

  forEachTd(startPoint, endPoint, callback) {
    for (let i = Math.min(startPoint.row, endPoint.row); i <= Math.max(startPoint.row, endPoint.row); i++) {
      for (let j = Math.min(startPoint.col, endPoint.col); j <= Math.max(startPoint.col, endPoint.col); j++) {
        let td = this.getTd(i, j);
        td && callback && callback(td);
      }
    }
  }

  forEachBoundTr(bound, callback) {
    this.forEachTr({row: bound.minRow, col: bound.minCol},
      {row: bound.maxRow, col: bound.maxCol}, callback);
  }

  forEachTr(startPoint, endPoint, callback) {
    for (let i = Math.min(startPoint.row, endPoint.row); i <= Math.max(startPoint.row, endPoint.row); i++) {
      let tr = this.getTr(i);
      tr && callback && callback(tr);
    }
  }

  getTr(row) {
    let res = $(this.root).find(`tr[data-row='${row}']`);
    return res[0];
  }

  getTd(row, col) {
    let res = $(this.root).find(`tr[data-row='${row}']`).find(`td[data-col='${col}']`);
    if (!res.length) return null;
    return res[0];
  }

  clearSelected() {
    $(this.root).find('.selected').removeClass('selected');
    $(this.root).find('.select-bound').remove();
    this.emitSelected();
  }

  isEditing(target) {
    return $(target).find('.content-wrapper').attr('contentEditable') === 'true'
      || $(target).attr('contentEditable') === 'true';
  }

  initWidth(el) {
    let cols = $(el).find('col');
    for (let i = 0; i < cols.length; i++) {
      let width = cols[i].offsetWidth;
      $(cols[i]).width(width);
    }
  }

  bindResize(el) {
    this.bindColSize(el);
    this.bindRowSize(el);
  }

  bindColSize(el) {
    let startX = 0, td = undefined, that = this, colResizePlaceholder = undefined;
    let endX = 0, width = 0;

    function mouseup() {
      removeResizeMask();
      $(colResizePlaceholder).remove();
      $(el).find('*').removeClass('col-resizing');
      that.setTdWidth(td, width);

      $(document.body).off('mouseup');
      $(document.body).off('mousemove');
      that.change();
    }

    function mousemove(e) {
      endX = e.clientX;
      let delta = endX - startX;
      let tempWidth = that.getTdWidth(td) + delta;
      if (tempWidth < 30) {
        tempWidth = 30;
        return;
      }
      width = tempWidth;
      $(colResizePlaceholder).find('.title').html(`列宽: ${tempWidth} 像素`);
      colResizePlaceholder.style.left = `${endX}px`;
    }

    function mousedown(e) {
      if (e.which !== 1) return;
      td = getTargetForTd(e.target);
      td = that.getResizingTd(td);
      if (!td) return;
      stop(e);
      that.clearSelected();
      that.closeContextMenu();

      startX = e.clientX;
      width = that.getTdWidth(td);
      colResizePlaceholder = createColResizePlaceholder(el);
      colResizePlaceholder.style.left = `${e.target.getBoundingClientRect().x + 2}px`;
      $(colResizePlaceholder).find('.title').html(`列宽: ${width} 像素`);
      createResizeMask('col-resizing');

      $(el).find('*').addClass('col-resizing');
      $(document.body).off('mouseup').on('mouseup', mouseup);
      $(document.body).off('mousemove').on('mousemove', mousemove);
    }

    $(el).find('.col-bar').off('mousedown').on('mousedown', mousedown);
  }

  bindRowSize(el) {
    let that = this, startY = 0, endY = 0, rowResizePlaceholder = undefined;
    let height = 0, td = undefined;

    function mousemove(e) {
      endY = e.clientY;
      let tempHeight = getTdHeight(td) + (endY - startY);
      if (tempHeight < 30) {
        tempHeight = 30;
        endY = startY - getTdHeight(td) + 30;
      }
      height = tempHeight;
      rowResizePlaceholder.style.top = `${endY}px`;
      $(rowResizePlaceholder).find('.title').html(`行高: ${height} 像素`);
    }

    function mouseup(e) {
      removeResizeMask();
      $(rowResizePlaceholder).remove();
      $(el).find('*').removeClass('row-resizing');
      setTdHeight(td, height);

      $(document.body).off('mouseup');
      $(document.body).off('mousemove');
      that.change();
    }

    function mousedown(e) {
      if (e.which !== 1) return;
      td = getTargetForTd(e.target);
      td = that.getResizingTd(td);
      if (!td) return;
      stop(e);
      that.clearSelected();
      that.closeContextMenu();
      startY = e.clientY;
      height = getTdHeight(td);
      createResizeMask('row-resizing');
      rowResizePlaceholder = createRowResizePlaceholder(el);
      // rowResizePlaceholder.style.top = `${startY}px`;
      rowResizePlaceholder.style.top = `${e.target.getBoundingClientRect().y + 2}px`;
      $(rowResizePlaceholder).find('.title').html(`行高: ${height} 像素`);

      $(el).find('*').addClass('row-resizing');
      $(document.body).off('mouseup').on('mouseup', mouseup);
      $(document.body).off('mousemove').on('mousemove', mousemove);
    }

    $(el).find('.row-bar').off('mousedown').on('mousedown', mousedown);
  }

  getTdSpan(td) {
    return {
      row: parseInt($(td).attr('rowspan') || '1'),
      col: parseInt($(td).attr('colspan') || '1'),
    };
  }

  getResizingTd(td) {
    let span = this.getTdSpan(td);
    return this.getTd(getTdRow(td) + span.row - 1, getTdCol(td) + span.col - 1);
  }

  getTdWidth(td) {
    let col = getTdCol(td);
    return $(this.root).find('col')[col].offsetWidth;
  }

  setTdWidth(td, width) {
    let col = getTdCol(td);
    $(this.root).find('col')[col].style.width = `${width}px`;
  }

  getCoverTd(td) {
    if (!td) return null;
    let row = getTdRow(td), col = getTdCol(td);
    let tds = $(this.root).find('td');
    for (let i = 0; i < tds.length; i++) {
      let coverTd = tds[i], span = this.getTdSpan(coverTd);
      if (isHideTd(coverTd)) continue;
      if ((getTdRow(coverTd) + span.row - 1 >= row)
        && (getTdRow(coverTd) <= row)
        && (getTdCol(coverTd) <= col)
        && (getTdCol(coverTd) + span.col - 1 >= col)) {
        return coverTd;
      }
    }
  }

  resetSelectedBound() {
    let bound = this.getSelectedBound();
    $(this.root).find('.selected').removeClass('selected');
    $(this.root).find('.select-bound').remove();
    this.setSelectedStyle({row: bound.minRow, col: bound.minCol}, {row: bound.maxRow, col: bound.maxCol});
  }

  disabledAction(action) {
    return this.getDisabledActions().has(action);
  }

  getDisabledActions() {
    let set = new Set();
    this.forEachBoundTd(this.getSelectedBound(), td => {
      if(isHideTd(td)) return;
      let dataActionDisabled = $(td).attr('data-action-disabled');
      if (dataActionDisabled) {
        let actions = dataActionDisabled.split(",") || [];
        actions.forEach(a => {
          if (a && (a = a.trim())) {
            set.add(a);
          }
        })
      }
    })
    return set;
  }
};

class Selection {
  constructor(root, action) {
    this.root = root;
    this.action = action;
  }

  getSelectedTds() {
    let action = this.action, res = [];
    action && action.forEachBoundTd(action.getSelectedBound(), td => {
      if (isHideTd(td)) return;
      res.push(td);
    });

    return res;
  }

  getStyle(key) {
    let action = this.action, style = undefined;
    action && action.forEachBoundTd(action.getSelectedBound(), td => {
      let s = window.getComputedStyle(td)[key] + '';
      if (style && style !== s) {
        style = undefined;
      } else {
        style = s;
      }
    });
    return style;
  }

  getWidth() {
    let action = this.action, that = this;
    let cols = $(this.action.root).find('col'), width;
    action && action.forEachBoundTd(action.getSelectedBound(), td => {
      let col = getTdCol(td);
      width = width || $(cols[col]).css('width');
    });
    return width;
  }

  setWidth(width) {
    let action = this.action, that = this;
    let cols = $(this.action.root).find('col');
    action && action.forEachBoundTd(action.getSelectedBound(), td => {
      let col = getTdCol(td);
      $(cols[col]).css('width', width);
      that.resetSelectedStyle();
    });
  }

  setStyle(key, value) {
    let action = this.action;
    action && action.forEachBoundTd(action.getSelectedBound(), td => {
      $(td).css(key, value);
    });
  }

  getTrStyle(key) {
    let action = this.action, style = undefined;
    action && action.forEachBoundTr(action.getSelectedBound(), tr => {
      let s = tr[key];
      if (style && style !== s) {
        style = undefined;
      } else {
        style = s;
      }
    });
    return style;
  }

  setTrStyle(key, value) {
    let action = this.action;
    action && action.forEachBoundTr(action.getSelectedBound(), tr => {
      $(tr).css(key, value);
    });
    this.resetSelectedStyle();
  }

  resetSelectedStyle() {
    this.action.resetSelectedBound();
  }

  getColor() {
    let action = this.action, color = undefined;
    action && action.forEachBoundTd(action.getSelectedBound(), td => {
      let s = $(td)[0].style.color;
      if (color && color !== s) {
        color = undefined;
      } else {
        color = s;
      }
    });
    return color;
  }

  getBackground() {
    let action = this.action, color = undefined;
    action && action.forEachBoundTd(action.getSelectedBound(), td => {
      let s = $(td)[0].style.background;
      if (color && color !== s) {
        color = undefined;
      } else {
        color = s;
      }
    });
    return color;
  }

  showBorderNone() {
    let action = this.action, bound = action.getSelectedBound();
    action && action.forEachBoundTd(bound, td => {
      // if (isHideTd(td)) return;
      this.doHideTopBorder(td);
      this.doHideBottomBorder(td);
      this.doHideLeftBorder(td);
      this.doHideRightBorder(td);
    });
  }

  showBorderAll() {
    this.showBorderNone();
    this.showBorderOutter();
    let action = this.action, bound = action.getSelectedBound();
    action && action.forEachBoundTd(bound, td => {
      // if (isHideTd(td)) return;
      this.doShowBorderTop(td);
      this.doShowBorderBottom(td);
      this.doShowBorderLeft(td);
      this.doShowBorderRight(td);
    });
  }

  showBorderOutter() {
    this.showBorderLeft();
    this.showBorderRight();
    this.showBorderTop();
    this.showBorderBottom();
  }

  showBorderInner() {
    this.showBorderAll();
    this.hideLeft();
    this.hideRight();
    this.hideTop();
    this.hideBottom();
  }

  showBorderLeft() {
    let action = this.action, bound = action.getSelectedBound();
    action && action.forEachBoundTd(bound, td => {
      // if (isHideTd(td)) return;
      let {col} = getPositionNumber(td);
      if (col === bound.minCol) {
        this.doShowBorderLeft(td, bound);
      }
    });
  }

  doShowBorderLeft(td) {
    let {col, row} = getPositionNumber(td);
    showTdBorder('border-left', td);
    let span = this.action.getTdSpan(td);
    for (let i = row; i < row + span.row; i++) {
      let leftTd = this.action.getCoverTd(this.action.getTd(i, col - 1));
      if (!leftTd) return;
      showTdBorder('border-right', leftTd);
    }
  }

  showBorderRight() {
    let action = this.action, bound = action.getSelectedBound();
    action && action.forEachBoundTd(bound, td => {
      // if (isHideTd(td)) return;
      let {col, row} = getPositionNumber(td);
      let span = this.action.getTdSpan(td);
      if (col + span.col - 1 === bound.maxCol) {
        this.doShowBorderRight(td);
      }
    });
  }

  doShowBorderRight(td) {
    let {col, row} = getPositionNumber(td);
    showTdBorder('border-right', td);
    let span = this.action.getTdSpan(td);
    for (let i = row; i < row + span.row; i++) {
      let rightTd = this.action.getCoverTd(this.action.getTd(i, col + span.col));
      if (!rightTd) return;
      showTdBorder('border-left', rightTd);
    }
  }

  showBorderTop() {
    let action = this.action, bound = action.getSelectedBound();
    action && action.forEachBoundTd(bound, td => {
      // if (isHideTd(td)) return;
      let {col, row} = getPositionNumber(td);
      if (row === bound.minRow) {
        this.doShowBorderTop(td);
      }
    });
  }

  doShowBorderTop(td) {
    let {col, row} = getPositionNumber(td);
    showTdBorder('border-top', td);
    let span = this.action.getTdSpan(td);
    for (let i = col; i < col + span.col; i++) {
      let topTd = this.action.getCoverTd(this.action.getTd(row - 1, i));
      if (!topTd) return;
      showTdBorder('border-bottom', topTd);
    }
  }

  showBorderBottom() {
    let action = this.action, bound = action.getSelectedBound();
    action && action.forEachBoundTd(bound, td => {
      // if (isHideTd(td)) return;
      let {col, row} = getPositionNumber(td);
      let span = this.action.getTdSpan(td);
      if (row + span.row - 1 === bound.maxRow) {
        this.doShowBorderBottom(td);
      }
    });
  }

  doShowBorderBottom(td) {
    let {col, row} = getPositionNumber(td);
    showTdBorder('border-bottom', td);
    let span = this.action.getTdSpan(td);
    for (let i = col; i < col + span.col; i++) {
      let topTd = this.action.getCoverTd(this.action.getTd(row + span.row, i));
      if (!topTd) return;
      showTdBorder('border-top', topTd);
    }
  }

  hideTop() {
    let action = this.action, bound = action.getSelectedBound();
    action && action.forEachBoundTd(bound, td => {
      // if (isHideTd(td)) return;
      let {col, row} = getPositionNumber(td);
      if (row === bound.minRow) {
        this.doHideTopBorder(td);
      }
    });
  }

  doHideTopBorder(td) {
    let {col, row} = getPositionNumber(td);
    hideTdBorder('border-top', td);
    hideTdBorder('border-bottom', this.action.getCoverTd(this.action.getTd(row - 1, col)));
  }

  hideBottom() {
    let action = this.action, bound = action.getSelectedBound();
    action && action.forEachBoundTd(bound, td => {
      // if (isHideTd(td)) return;
      let {col, row} = getPositionNumber(td);
      let span = this.action.getTdSpan(td);
      if (bound.maxRow === row + span.row - 1) {
        this.doHideBottomBorder(td);
      }
    });
  }

  doHideBottomBorder(td) {
    let {col, row} = getPositionNumber(td);
    hideTdBorder('border-bottom', td);
    hideTdBorder('border-top', this.action.getCoverTd(this.action.getTd(row + 1, col)));
  }

  hideLeft() {
    let action = this.action, bound = action.getSelectedBound();
    action && action.forEachBoundTd(bound, td => {
      // if (isHideTd(td)) return;
      let {col, row} = getPositionNumber(td);
      if (col === bound.minCol) {
        this.doHideLeftBorder(td);
      }
    });
  }

  doHideLeftBorder(td) {
    let {col, row} = getPositionNumber(td);
    hideTdBorder('border-left', td);
    hideTdBorder('border-right', this.action.getCoverTd(this.action.getTd(row, col - 1)));
  }

  hideRight() {
    let action = this.action, bound = action.getSelectedBound();
    action && action.forEachBoundTd(bound, td => {
      // if (isHideTd(td)) return;
      let {col, row} = getPositionNumber(td);
      let span = this.action.getTdSpan(td);
      if (col + span.col - 1 === bound.maxCol) {
        this.doHideRightBorder(td, bound);
      }
    });
  }

  doHideRightBorder(td) {
    let {col, row} = getPositionNumber(td);
    hideTdBorder('border-right', td);
    hideTdBorder('border-left', this.action.getCoverTd(this.action.getTd(row, col + 1)));
  }

  hideOutter() {
    this.hideTop();
    this.hideBottom();
    this.hideLeft();
    this.hideRight();
  }

  hideInner() {
    let action = this.action, bound = action.getSelectedBound();
    action && action.forEachBoundTd(bound, td => {
      // if (isHideTd(td)) return;
      let {col, row} = getPosition(td);
      let span = action.getTdSpan(td);
      if (col + span.col - 1 < bound.maxCol) {
        this.doHideRightBorder(td);
      }
      if (row + span.row - 1 < bound.maxRow) {
        this.doHideBottomBorder(td);
      }
      if (row > bound.minRow) {
        this.doHideTopBorder(td);
      }
      if (col > bound.minCol) {
        this.doHideLeftBorder(td);
      }
    });
  }

  getDisabledActions() {
    return this.action.getDisabledActions();
  }
}
